#include <geekos/errno.h>
#include <geekos/kassert.h>
#include <geekos/ktypes.h>
#include <geekos/screen.h>  /* for debug Print() statements */
#include <geekos/pfat.h>
#include <geekos/malloc.h>
#include <geekos/string.h>
#include <geekos/user.h>
#include <geekos/kthread.h>
#include <geekos/defs.h>
#include <geekos/mem.h>
#include <geekos/int.h>
#include <geekos/gdt.h>
#include <geekos/segment.h>
#include <geekos/tss.h>
#include <geekos/argblock.h>
#include <geekos/vfs.h>
#include <geekos/sort.h>

#define safe_Free(X) \
  if (X != NULL) \
  { \
    Free(X); \
    X = NULL; \
  }
  
#include <geekos/load_elf_exe.h>


static struct User_Context* G_Create_User_Context(ulong_t size)
{
    struct User_Context * UserContext;

    size = Round_Up_To_Page(size);
    UserContext = (struct User_Context *)Malloc(sizeof(struct User_Context));

    if (UserContext != 0){
        UserContext->memory = Malloc(size);
    }
    else{
        goto fail;
    }

    if (UserContext->memory == 0){
        goto fail;
    }

    memset(UserContext->memory, '\0', size);

    UserContext->size = size;

    /* Allocate an LDT descriptor for the user context */
    UserContext->ldtDescriptor = Allocate_Segment_Descriptor();
    if (UserContext->ldtDescriptor == 0){
        goto fail;
    }
   
    Init_LDT_Descriptor(UserContext->ldtDescriptor, UserContext->ldt, NUM_USER_LDT_ENTRIES);

    
    UserContext->ldtSelector = Selector(KERNEL_PRIVILEGE, true, Get_Descriptor_Index(UserContext->ldtDescriptor));

    /* Initialize code and data segments within the LDT */
    Init_Code_Segment_Descriptor(
        &UserContext->ldt[0],
        (ulong_t) UserContext->memory,
        size / PAGE_SIZE,
        USER_PRIVILEGE
    );
    Init_Data_Segment_Descriptor(
        &UserContext->ldt[1],
        (ulong_t) UserContext->memory,
        size / PAGE_SIZE,
        USER_PRIVILEGE
    );
    UserContext->csSelector = Selector(USER_PRIVILEGE, false, 0);
    UserContext->dsSelector = Selector(USER_PRIVILEGE, false, 1);

    UserContext->refCount = 0;  

    return UserContext;

fail:
    if (UserContext != 0)
    {
        if (UserContext->memory != 0)
            Free(UserContext->memory);
        Free(UserContext);
    }

    return 0;
}

inline static int G_memcmp(const void *s1_, const void *s2_, size_t n)
{
    const signed char *s1 = s1_, *s2 = s2_;

    while (n > 0) 
    {
        if(*s1 != *s2)
            return -1;
        ++s1;
        ++s2;
        --n;
    }
    return 0;
}

inline static size_t G_strlen(const char* s)
{
    size_t len = 0;
    while (*s++ != '\0')
    ++len;
    return len;
}

int load_elf_exe(const char *program, const char *command, struct Kernel_Thread **pThread)
{
	int retval = -1;

    int load_exe_only_flag = 0;

	int i = 0;

	Elf32_Ehdr *exe_ehdr = 0;
    Elf32_Shdr *exe_shdr = 0;
    Elf32_Phdr *exe_phdr = 0;
    char *exe_strtab;

    ulong_t exe_file_len;
    char *exe_file_data = 0;

    Rel_info exe_rel_info;

    ulong_t exe_maxva = 0;

    int have_so_flag = 0;


    // char *so_file_name = 0;
    char **dynsym_so = 0;
    int so_cnt = 0;
    Elf32_Ehdr *so_ehdr = 0;
    // Elf32_Shdr *so_shdr = 0;
    Elf32_Phdr *so_phdr = 0;

    ulong_t so_file_len;
    char *so_file_data = 0;

    Sym_info so_sym_info;

    ulong_t so_maxva = 0;

    unsigned numArgs;
    ulong_t argBlockSize;
    ulong_t virtSize, argBlockAddr;
    struct User_Context *userContext = 0;

    struct Kernel_Thread * thread;

    // read exe file
    if (0 != (retval = Read_Fully(program, (void **)&exe_file_data, 
    	&exe_file_len)))
	{
		goto out;
	}

    // init exe_ehdr
	exe_ehdr = (Elf32_Ehdr*)exe_file_data;

    // is ELF?
	if(G_memcmp((void*)exe_ehdr->e_ident, (void*)ELFMAG, SELFMAG) != 0)
		goto out;
    
    // is ELF executeble
	if(exe_ehdr->e_type != ET_EXEC)
		goto out;

	exe_shdr = (Elf32_Shdr*)(exe_file_data + exe_ehdr->e_shoff);
	exe_phdr = (Elf32_Phdr*)(exe_file_data + exe_ehdr->e_phoff);
    exe_strtab = (char*)(exe_file_data + exe_shdr[exe_ehdr->e_shstrndx].sh_offset); 

    // find exe mva top
    for(i = 0; i < exe_ehdr->e_phnum; i++)
    {
        ulong_t topva = exe_phdr[i].p_vaddr + exe_phdr[i].p_memsz;
        if(topva > exe_maxva){
            exe_maxva = topva;
        }
    }

    for(i = 0; i < exe_ehdr->e_phnum; ++i){
        if(PT_INTERP == exe_phdr[i].p_type){
            have_so_flag = 1;
        }
    }
    
    if(0 == have_so_flag){
        so_maxva = exe_maxva;
        load_exe_only_flag = 1;
        goto load_exe_oly;        
    }
    

    // get so file names
    retval = get_dynsym_so(exe_file_data, exe_ehdr, exe_shdr, exe_strtab, &dynsym_so, 
                    &so_cnt);
    if(0 != retval){
        goto out;
    }

    if (0 != (retval = Read_Fully(dynsym_so[0], (void **)&so_file_data, 
    	&so_file_len))){
		goto out;
	}

    // init so ehdr
	so_ehdr = (Elf32_Ehdr*)so_file_data;
    if(0 == so_ehdr){
        goto out;
    }

    // is ELF
	if(G_memcmp((void*)so_ehdr->e_ident, (void*)ELFMAG, SELFMAG) != 0){
		goto out;
    }
    // is ET_DYN
	if(so_ehdr->e_type != ET_DYN){
		goto out;
    }

    retval = rel_sos(&so_file_data, so_ehdr, Round_Up_To_Page(exe_maxva));
    if(-1 == retval){
        goto out;
    }

    so_phdr = (Elf32_Phdr*)(so_file_data + so_ehdr->e_phoff);
    if(0 == so_phdr){
        goto out;
    }
    // find so mva top
    for(i = 0; i < so_ehdr->e_phnum; i++)
    {
        ulong_t topva = so_phdr[i].p_vaddr + so_phdr[i].p_memsz;
        if(topva > so_maxva)
            so_maxva = topva;
    }

    retval = get_Sym_info(so_file_data, &so_sym_info);
    if(-1 == retval)
    {
        goto out;
    }

    retval = get_Rel_info(exe_file_data, exe_ehdr, exe_shdr, exe_strtab,
                             &exe_rel_info);
    if(-1 == retval){
        goto out;
    }

    // rel exe outsite func
    retval = rel_exe_func(&exe_file_data, &exe_rel_info, &so_sym_info);
    if(0 != retval){
        goto out;
    }

load_exe_oly:
    
    Get_Argument_Block_Size(command, &numArgs, &argBlockSize);
    virtSize = Round_Up_To_Page(so_maxva) + DEFAULT_USER_STACK_SIZE;
    argBlockAddr = virtSize;
    virtSize += argBlockSize;
    userContext = G_Create_User_Context(virtSize);
    if(0 == userContext){
        goto out;
    }

    // maping exe_data
    for(i = 0; i < exe_ehdr->e_phnum; i++)
    {
        if(PT_LOAD == exe_phdr[i].p_type){
            memcpy((userContext->memory + exe_phdr[i].p_vaddr), (exe_file_data + 
                exe_phdr[i].p_offset), exe_phdr[i].p_filesz);
        }
    }
    
    if(0 == load_exe_only_flag)
    {
        int tmp = 0;
        // maping so_data
        for(i = 0; i < so_ehdr->e_phnum; i++)
        {
            if(PT_LOAD == so_phdr[i].p_type){
                memcpy((userContext->memory + so_phdr[i].p_vaddr), (so_file_data + 
                    so_phdr[i].p_offset), so_phdr[i].p_filesz);
                ++tmp;
            }
        }
    }

    Format_Argument_Block(userContext->memory + argBlockAddr, numArgs, 
        argBlockAddr, command);

    userContext->argBlockAddr = argBlockAddr;
    userContext->stackPointerAddr = argBlockAddr;

    userContext->entryAddr = exe_ehdr->e_entry; 
    
    thread = Start_User_Thread(userContext, false);
    if(thread != 0){
        KASSERT(thread->refCount == 2);
        *pThread = thread;
        retval = thread->pid;
    }
    else 
    {
        retval = ENOMEM;
    }   

	safe_Free(exe_file_data);
    if(0 == load_exe_only_flag){

        free_Rel_info(&exe_rel_info);

        free_Sym_info(&so_sym_info);
        safe_Free(so_file_data);
    }

	return retval;

out:
    
	safe_Free(exe_file_data);

	safe_Free(so_file_data);

    if(0 != userContext){
        Destroy_User_Context(userContext);
    }

	return retval;
}

int get_exe_got_plt_base(Elf32_Shdr* exe_shdr, char *exe_strtab, int num, 
    Elf32_Addr *base)
{
    int retval = -1;
    int i = 0;
    int flag = 0;

    if(!exe_shdr || !exe_strtab){
        goto out;
    }

    for(; i < num; ++i){
        if(0 == G_memcmp(GOT_PLT_SCTIONG_NAME, (exe_strtab + exe_shdr[i].sh_name), 
            G_strlen(GOT_PLT_SCTIONG_NAME)) && SHT_PROGBITS == exe_shdr[i].sh_type){
            *base = exe_shdr[i].sh_addr;
            flag = 1;
        }
    }

    if(1 == flag){
        retval = 1;
    }
    else{
         retval = 0;
    }
    return retval;

out:
    return retval;
}

int rel_exe(char **exe_file_data, Elf32_Ehdr *exe_ehdr, 
    Elf32_Addr *exe_old_sym_abs_offset)
{
    int retval = -1;
    int i = 0;
    int got_plt_flag;

    Elf32_Shdr* exe_shdr;
    Elf32_Phdr* exe_phdr;
    char *exe_strtab;

    Elf32_Addr *sh_ad_ls;
    Elf32_Addr *ph_ad_ls;

    Elf32_Addr got_plt_base;

    if(0 == (*exe_file_data) || 0 == exe_ehdr){
        goto out;
    }

    exe_shdr = (Elf32_Shdr*)((*exe_file_data) + exe_ehdr->e_shoff);
    if(0 == exe_shdr){
        goto out;
    }

    exe_strtab = (char*)((*exe_file_data) + exe_shdr[exe_ehdr->e_shstrndx].sh_offset);
    if(0 == exe_strtab){
        goto out;
    }

    retval = get_exe_got_plt_base(exe_shdr, exe_strtab, exe_ehdr->e_shnum, &got_plt_base);
    if(0 == retval){
        got_plt_flag = 0;
    }
    else if(1 == retval){
        got_plt_flag = 1;
    }
    else{
        goto out;
    }

    if(1 == got_plt_flag){
        sh_ad_ls = (Elf32_Addr*)Malloc(sizeof(Elf32_Addr) * (exe_ehdr->e_shnum));
        if(0 == sh_ad_ls){
            goto out;
        }
        memset(sh_ad_ls, 0, sizeof(Elf32_Addr) * exe_ehdr->e_shnum);
        for(; i < exe_ehdr->e_shnum; ++i){
            sh_ad_ls[i] = exe_shdr[i].sh_addr;
        }

        MergeSort(sh_ad_ls, exe_ehdr->e_shnum);        
        retval = rel_exe_sh(&exe_shdr, sh_ad_ls, exe_ehdr->e_shnum);
        if(0 != retval){
            goto out;
        }

        retval = rel_exe_rel(exe_file_data, exe_shdr, exe_strtab, exe_ehdr->e_shnum, 
            got_plt_base);
        safe_Free(sh_ad_ls);
        if(0 != retval){
            goto out;
        }

        retval = rel_exe_sym(exe_file_data, exe_shdr, exe_ehdr->e_shnum, 
            exe_strtab, exe_old_sym_abs_offset);
        if(0 != retval){
            goto out;
        }

        exe_phdr = (Elf32_Phdr*)((*exe_file_data) + exe_ehdr->e_phoff);
        if(0 == exe_phdr){
            goto out;
        }

        ph_ad_ls = (Elf32_Addr*)Malloc(sizeof(Elf32_Addr) * (exe_ehdr->e_phnum));
        if(0 == ph_ad_ls){
            goto out;
        }

        memset(ph_ad_ls, 0, (sizeof(Elf32_Addr) * exe_ehdr->e_phnum));
        for(i = 0; i < exe_ehdr->e_phnum; ++i){
            ph_ad_ls[i] = exe_phdr[i].p_vaddr;
        }

        MergeSort(ph_ad_ls, exe_ehdr->e_phnum);

        retval = rel_exe_ph(&exe_phdr, ph_ad_ls, exe_ehdr->e_phnum);

        safe_Free(ph_ad_ls);
        if(0 != retval){
            goto out;
        }        
    }
    

    retval = 0;
    return retval;

out:

    return retval;
}

int rel_exe_sh(Elf32_Shdr **exe_shdr, Elf32_Addr *exe_sh_ad_ls, int num)
{
    int i = 0;
    int j = 0;
    int retval = -1;
    Elf32_Addr pre_addr = 0;
    Elf32_Word pre_size = 0;

    int *flag;

    if(!(*exe_shdr) || !exe_sh_ad_ls){
        goto out;
    }

    flag = (int*)Malloc(sizeof(int) * num);
    if(0 == flag){
        goto out;
    }
    memset(flag, 0, sizeof(int) * num);
    for(i = 0; i < num; ++i){
        flag[i] = 0;
    }

    for(i = 0; i < num; ++i){
        for(j = 0; j < num; ++j){
            if((*exe_shdr)[j].sh_addr == exe_sh_ad_ls[i] && 0 == flag[j]){
                (*exe_shdr)[j].sh_addr = pre_addr + pre_size;

                pre_addr = ((*exe_shdr)[j].sh_addr);

                if(0 == (*exe_shdr)[j].sh_addralign){
                    (*exe_shdr)[j].sh_addralign = 0x10;
                }

                if((*exe_shdr)[j].sh_size < (*exe_shdr)[j].sh_addralign){
                    pre_size = (*exe_shdr)[j].sh_addralign;
                }
                else if((*exe_shdr)[j].sh_size > (*exe_shdr)[j].sh_addralign &&
                    0 != ((*exe_shdr)[j].sh_size % (*exe_shdr)[j].sh_addralign)){

                    pre_size = (*exe_shdr)[j].sh_size + ((*exe_shdr)[j].sh_addralign
                                - ((*exe_shdr)[j].sh_size % (*exe_shdr)[j].sh_addralign));
                }
                else{
                    pre_size = ((*exe_shdr)[j].sh_size);
                }
                flag[j] = 1;
            }
        }
    }

    safe_Free(flag);
    retval = 0;
    return retval;

out:
    return retval;
}

int rel_exe_rel(char **exe_file_data, Elf32_Shdr *exe_shdr, char *exe_strtab, 
    int num, Elf32_Addr old_got_plt_base)
{
    int retval = -1;
    int i = 0;
    int j = 0;
    Elf32_Addr new_got_plt_base = 0; 

    if(!(*exe_file_data) || !exe_shdr || !exe_strtab){
        goto out;
    }

    retval = get_exe_got_plt_base(exe_shdr, exe_strtab, num, &new_got_plt_base);
    if(!retval){
        goto out;
    }

    for(; i < num; ++i){
        if((0 == G_memcmp(REL_PLT_SCTIONG_NAME, (exe_strtab + exe_shdr[i].sh_name), 
            G_strlen(REL_PLT_SCTIONG_NAME))) && SHT_REL == exe_shdr[i].sh_type){
            
            Elf32_Rel *tmp_rel = (Elf32_Rel*)(exe_file_data + exe_shdr[i].sh_offset);
            int rel_num = (exe_shdr[i].sh_size / exe_shdr[i].sh_entsize);
            if(99 <= rel_num || 0 == rel_num){
                goto out;
            }

            for(; j < rel_num; ++j){
                tmp_rel[j].r_offset = new_got_plt_base + (tmp_rel[j].r_offset - 
                    old_got_plt_base);
            }

        }
    }

    retval = 0;
    return retval;

out:
    
    return retval;
}

int rel_exe_sym(char **exe_file_data, Elf32_Shdr *exe_shdr, int num, 
    char *exe_strtab, Elf32_Addr *exe_old_sym_abs_offset)
{
    int retval = -1;
    int i = 0;
    int j = 0;

    if(0 == (*exe_file_data) || 0 == exe_shdr || 0 == num || 0 == exe_strtab){
        goto out;
    }

   for(i = 0; i < num; ++i){
        if((SHT_DYNSYM == exe_shdr[i].sh_type) && (0 == G_memcmp((exe_strtab + 
            exe_shdr[i].sh_name), DYNSYM_SCTIONG_NAME, 
            G_strlen(DYNSYM_SCTIONG_NAME)))){

            Elf32_Sym *tmp_sym = (Elf32_Sym *)((*exe_file_data) + exe_shdr[i].sh_offset);
            if(0 == tmp_sym){
                goto out;
            }

            int sym_num = (exe_shdr[i].sh_size / exe_shdr[i].sh_entsize);
            if(99 <= sym_num || 0 == sym_num){
                goto out;
            }

            for(j = 0; j < sym_num; ++j){
                if(SHN_COMMON != tmp_sym[j].st_shndx && 
                    SHN_UNDEF != tmp_sym[j].st_shndx){

                    tmp_sym[j].st_value = exe_shdr[tmp_sym[j].st_shndx].sh_addr 
                                            + exe_old_sym_abs_offset[j];

                }
                else{
                    tmp_sym[j].st_value = 0x0;
                }

            }
            retval = 0;
        }
    }

    return retval;

out:
    return retval;
}

int rel_exe_ph(Elf32_Phdr **exe_phdr, Elf32_Addr *exe_ph_ad_ls, int num)
{
    int i = 0;
    int j = 0;
    int retval = 0;
    int *flag;

    Elf32_Addr pre_addr = 0;
    Elf32_Word pre_size = 0;

    if(!(*exe_phdr) || !(exe_ph_ad_ls)){
        goto out;
    }

    flag = (int*)Malloc(sizeof(int) * num);
    if(0 == flag){
        goto out;
    }
    memset(flag, 0, sizeof(int) * num);
    for(i = 0; i < num; ++i){
        flag[i] = 0;
    }

    for(i = 0; i < num; ++i){
        for(j = 0; j < num; ++j){
            if(exe_ph_ad_ls[i] == (*exe_phdr)[j].p_vaddr && 0 == flag[j] ){

                (*exe_phdr)[j].p_vaddr = pre_addr + pre_size;
                (*exe_phdr)[j].p_paddr = (*exe_phdr)[j].p_vaddr;
                pre_addr = (*exe_phdr)[j].p_vaddr;
                
                if((*exe_phdr)[j].p_memsz < (*exe_phdr)[j].p_align){
                    pre_size = (*exe_phdr)[j].p_align;
                }
                else if((*exe_phdr)[j].p_memsz > (*exe_phdr)[j].p_align && 
                    (0 != ((*exe_phdr)[j].p_memsz % (*exe_phdr)[j].p_align))){
                    pre_size = (*exe_phdr)[j].p_memsz + ((*exe_phdr)[j].p_align - 
                        ((*exe_phdr)[j].p_memsz % (*exe_phdr)[j].p_align));
                }
                else{
                    pre_size = (*exe_phdr)[j].p_memsz;
                }
                flag[j] = 1;
            }
        }
    }

    safe_Free(flag);
    retval = 0;
    return retval;

out:
    return retval;    
}

int rel_sos(char **so_file_data, Elf32_Ehdr *so_ehdr, Elf32_Addr so_base_ad)
{
    int retval = -1;
    int i = 0;
    Elf32_Sym *so_dynsym = 0;
    int so_dynsym_num = 0;

    Elf32_Shdr* so_shdr;
    Elf32_Phdr* so_phdr;
    char *so_strtab;

    if(0 == *so_file_data || 0 == so_ehdr){
        goto out;
    }
    so_shdr = (Elf32_Shdr*)(*so_file_data + so_ehdr->e_shoff);
    so_phdr = (Elf32_Phdr*)(*so_file_data + so_ehdr->e_phoff);
    so_strtab = (char*)(*so_file_data + so_shdr[so_ehdr->e_shstrndx].sh_offset);

    if(0 == so_strtab){
        goto out;
    }  

    retval = rel_so_phdrs(&so_phdr, so_ehdr->e_phnum, so_base_ad);
    if(-1 == retval){
        goto out;
    }

    // get so dynsym table
    for(i = 0; i < so_ehdr->e_shnum; i++)
    {
        if((SHT_DYNSYM == so_shdr[i].sh_type) && 
            (0 == G_memcmp((const char*)(so_strtab + so_shdr[i].sh_name), 
                DYNSYM_SCTIONG_NAME, G_strlen(DYNSYM_SCTIONG_NAME))))
        {
            so_dynsym = (Elf32_Sym*)(*so_file_data + so_shdr[i].sh_offset);
            if(0 == so_dynsym){
                goto out;
            }
            so_dynsym_num = (so_shdr[i].sh_size / so_shdr[i].sh_entsize);
            if(0 == so_dynsym_num || 999 <= so_dynsym_num){
                goto out;
            }
        }
    } 

    retval = rel_so_syms(&so_dynsym, so_dynsym_num, so_base_ad);
    if(-1 == retval){
        goto out;
    }

    retval = 0;
    return retval;

out:
    return retval;
}

int rel_so_sh(Elf32_Shdr **shdr, int sec_num, Elf32_Addr base_ad)
{
    int retval = -1;
    int i = 0;
    int j = 0;

    Elf32_Addr pre_addr = 0;
    Elf32_Word pre_size = 0;

    Elf32_Addr *sh_ad_ls;

    int *flag;    

    if(0 == *shdr || 999 <= sec_num || 0 == base_ad){
        goto out;
    }

    flag = (int*)Malloc(sizeof(int) * sec_num);
    if(0 == flag){
        goto out;
    }
    memset(flag, 0, sizeof(int) * sec_num);
    for(i = 0; i < sec_num; ++i){
        flag[i] = 0;
    }

    sh_ad_ls = (Elf32_Addr*)Malloc(sizeof(Elf32_Addr) * sec_num);
    if(0 == sh_ad_ls){
        goto out;
    }
    memset(sh_ad_ls, 0, sizeof(Elf32_Addr) * sec_num);
    for(i = 0; i < sec_num; ++i){
        sh_ad_ls[i] = (*shdr)[i].sh_addr;
    }  
    MergeSort(sh_ad_ls, sec_num);

    for(i = 0; i < sec_num; ++i){
        for(j = 0; j < sec_num; ++j){
            if((*shdr)[j].sh_addr == sh_ad_ls[i] && 0 == flag[j]){

                (*shdr)[j].sh_addr = base_ad + (pre_addr + pre_size);

                pre_addr = (*shdr)[j].sh_addr;

                if(0 == (*shdr)[j].sh_addralign){
                    (*shdr)[j].sh_addralign = 0x10;
                }

                if((*shdr)[j].sh_size < (*shdr)[j].sh_addralign){
                    pre_size = (*shdr)[j].sh_addralign;
                }
                else if((*shdr)[j].sh_size > (*shdr)[j].sh_addralign &&
                        0 != ((*shdr)[j].sh_size % (*shdr)[j].sh_addralign)){
                    pre_size = (*shdr)[j].sh_size + ((*shdr)[j].sh_addralign -
                        ((*shdr)[j].sh_size % (*shdr)[j].sh_addralign));
                }
                else{
                    pre_size = (*shdr)[j].sh_size;
                }
                flag[j] = 1;
            }
        }
    }

    safe_Free(sh_ad_ls);
    safe_Free(flag);
    retval = i;
    return retval;

out:
    return retval;
}

int rel_so_syms(Elf32_Sym **syms, int sym_num, Elf32_Addr base_ad)
{
    int retval = -1;
    int i = 0;

    if(0 == *syms || 999 <= sym_num || 0 == base_ad){
        goto out;
    }

    
    for (i = 0; i < sym_num; i++){
        if(STT_FUNC == ELF32_ST_TYPE((*syms)[i].st_info)){
            (*syms)[i].st_value = base_ad + (*syms)[i].st_value;
        }
    }

    retval = 0;
    return retval;    

out:
    return retval;
}

int rel_rel(Elf32_Rel **rel, int rel_num, Elf32_Addr base_ad)
{
    int retval = -1;
    if(0 == *rel || 999 <= rel_num || 0 == base_ad)
        goto out;

    int i = 0;
    for (; i < rel_num; i++)
    {
        (*rel)[i].r_offset = base_ad + (*rel)[i].r_offset;
    }

    retval = i;
    return retval;    
out:
    return retval;    
}

int rel_so_phdrs(Elf32_Phdr **phdr, int sege_num, Elf32_Addr base_ad)
{
    int i = 0;
    int retval = 0;

    if(0 == *phdr || 999 <= sege_num || 0 == base_ad){
        goto out;
    }

    for (i = 0; i < sege_num; ++i){
        if(PT_LOAD == (*phdr)[i].p_type){
            (*phdr)[i].p_vaddr = base_ad + (*phdr)[i].p_vaddr;
            (*phdr)[i].p_paddr = (*phdr)[i].p_vaddr;
        }
    }

    retval = i;
    return retval;

out:
    return retval;        
}

int get_exe_old_dyn_info(char *exe_file_data, Elf32_Ehdr *exe_ehdr, 
    Elf32_Shdr *exe_shdr, char *exe_strtab, Rel_info *rel_info, 
    Elf32_Addr **exe_old_sym_abs_offset)
{
    int retval = -1;

    if(0 == exe_file_data || 0 == exe_ehdr || 0 == exe_shdr || 0 == exe_strtab){
        goto out;
    }

    retval = get_Rel_info(exe_file_data, exe_ehdr, exe_shdr, exe_strtab, 
        rel_info);

    if(0 == retval || -1 == retval){
        goto out;
    }

    retval = get_exe_old_sym(exe_file_data, exe_ehdr, exe_shdr, exe_strtab,
                                    exe_old_sym_abs_offset);
    if(0 == retval || -1 == retval){

        goto out;
    }


    return retval;

out:
    return retval;
}

int get_Rel_info(char *file_data, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr, 
    char *strtab, Rel_info *rel_info)
{
    int retval = -1;
    int i = 0;
    int j = 0;
    int flag = 0; 
    Elf32_Addr sub_offset = 0;

    if(0 == file_data || 0 == ehdr || 0 == shdr || 0 == strtab){
        goto out;
    }

    for(i = 0; i < ehdr->e_shnum; i++){
        if(0 == (G_memcmp((strtab + shdr[i].sh_name), GOT_PLT_SCTIONG_NAME, 
            G_strlen(GOT_PLT_SCTIONG_NAME)))){
            sub_offset = shdr[i].sh_addr - shdr[i].sh_offset;
        }
    }


    for(i = 0; i < ehdr->e_shnum; i++)
    {
        if(0 == (G_memcmp((strtab + shdr[i].sh_name), REL_PLT_SCTIONG_NAME, 
            G_strlen(REL_PLT_SCTIONG_NAME))))
        {        
            
            flag = 1;
            Elf32_Rel *tmp_rel = (Elf32_Rel*)(file_data + shdr[i].sh_offset);
            int rel_num = (shdr[i].sh_size / shdr[i].sh_entsize);
            if(99 <= rel_num || 0 == rel_num)
                goto out;

            Elf32_Sym *tmp_sym = (Elf32_Sym*)(file_data + 
                shdr[shdr[i].sh_link].sh_offset);

            char *func_name = (char*)(file_data + 
                (shdr[shdr[shdr[i].sh_link].sh_link].sh_offset));
            if(0 == func_name)
                goto out;
            (*rel_info).num = rel_num;

            (*rel_info).func_name = (char**)Malloc(sizeof(char*) * rel_num);
            if(!rel_info->func_name)
                goto out_free;
            memset((*rel_info).func_name, 0, sizeof(char**)*rel_num);

            (*rel_info).r_offset = (Elf32_Addr*)Malloc(sizeof(Elf32_Addr) * rel_num);
            if(!(*rel_info).r_offset)
                goto out_free;
            memset((*rel_info).r_offset, 0, sizeof(Elf32_Addr) * rel_num);

            for(; j < rel_num; j++)
            {
                (*rel_info).func_name[j] = (func_name + 
                        tmp_sym[ELF32_R_SYM(tmp_rel[j].r_info)].st_name);

                (*rel_info).r_offset[j] = tmp_rel[j].r_offset - sub_offset;
            }
            retval = 1;
        }
    }
    if(0 == flag)
    {
        (*rel_info).num = 0;
        (*rel_info).func_name = 0;
        (*rel_info).r_offset = 0;
        retval = 0;
        return retval;
    }
    return retval;

out:
    (*rel_info).num = 0;
    (*rel_info).func_name = 0;
    (*rel_info).r_offset = 0;
    return retval;

out_free:
    (*rel_info).num = 0;
    free_Rel_info(rel_info);
    return retval;
}

void free_Rel_info(Rel_info *rel_info)
{
    safe_Free((*rel_info).func_name);
    safe_Free((*rel_info).r_offset);
}

int get_exe_old_sym(char *exe_file_data, Elf32_Ehdr *exe_ehdr, 
    Elf32_Shdr *exe_shdr, char *exe_strtab, Elf32_Addr **exe_old_sym_abs_offset)
{
    int retval = -1;
    int i = 0;
    int j = 0;
    int flag = 0;

    if(0 == exe_file_data || 0 == exe_ehdr || 0 == exe_shdr || 0 == exe_strtab){
        goto out;
    }

    for(i = 0; i < exe_ehdr->e_shnum; ++i){
        if((SHT_DYNSYM == exe_shdr[i].sh_type) && (0 == G_memcmp((exe_strtab + 
            exe_shdr[i].sh_name), DYNSYM_SCTIONG_NAME, 
            G_strlen(DYNSYM_SCTIONG_NAME)))){

            flag = 1;
            Elf32_Sym *tmp_sym = (Elf32_Sym *)(exe_file_data + exe_shdr[i].sh_offset);
            if(0 == tmp_sym){
                goto out;
            }

            int sym_num = (exe_shdr[i].sh_size / exe_shdr[i].sh_entsize);
            if(0 == sym_num){
                goto out;
            }

            if(99 <= sym_num || 0 == sym_num){
                goto out;
            }

            (*exe_old_sym_abs_offset) = (Elf32_Addr*)Malloc(sizeof(Elf32_Addr) * sym_num);
            if(0 == (*exe_old_sym_abs_offset)){
                goto out;
            }
            memset((*exe_old_sym_abs_offset), 0, (sizeof(Elf32_Addr) * sym_num));

            for(j = 0; j < sym_num; ++j){
                if(SHN_COMMON != tmp_sym[j].st_shndx && 
                    SHN_UNDEF != tmp_sym[j].st_shndx){

                    (*exe_old_sym_abs_offset)[j] = (tmp_sym[j].st_value - 
                        exe_shdr[tmp_sym[j].st_shndx].sh_addr);

                }
                else{
                    (*exe_old_sym_abs_offset)[j] = 0x0;
                }
            }
            retval = 1;            
        }
    }

    if(0 == flag){
        retval = 0; 
        return retval;
    }

    return retval;
out:
    return retval;          
}

int get_dynsym_so(char *exe_file_data, Elf32_Ehdr *exe_ehdr, 
    Elf32_Shdr *exe_shdr, char *exe_strtab, char ***dynsym_so, int *cnt)
{
    int retval = -1;
    int i = 0;
    int j = 0;
    Elf32_Dyn *dyn;
    int dyn_num = 0;
    int dyn_need_num = 0;
    char *dyn_str = 0;

    if(0 == exe_file_data || 0 == exe_ehdr || 0 == exe_shdr || 0 == exe_strtab){
        goto out;
    }

    for(i = 0; i < exe_ehdr->e_shnum; i++){
        if(0 == (G_memcmp((exe_strtab + exe_shdr[i].sh_name), 
            DYNAMIC_SCTIONG_NAME, 
            G_strlen(DYNAMIC_SCTIONG_NAME)))){
            dyn = (Elf32_Dyn*)(exe_file_data + exe_shdr[i].sh_offset);
            if(0 == dyn){
                goto out;
            }

            dyn_num = (exe_shdr[i].sh_size / exe_shdr[i].sh_entsize);
            if(0 == dyn_num || 99 < dyn_num){
                goto out;
            }

            dyn_str = (char*)(exe_file_data + 
                        exe_shdr[exe_shdr[i].sh_link].sh_offset);
            if(0 == dyn_str){
                goto out;
            }
        }
    }
    for(i = 0; i < dyn_num; ++i){
        if(DT_NEEDED == dyn[i].d_tag){
            ++dyn_need_num;
        }

    }

    (*dynsym_so) = (char**)Malloc(sizeof(char*) * dyn_need_num);
    if(0 == dynsym_so){
        goto free_out;
    }
    memset(dynsym_so, 0, (sizeof(char*) * dyn_need_num));

    for(i = 0; i < dyn_num; ++i){
        if(DT_NEEDED == dyn[i].d_tag){
            
            char *tmp_str = (char*)(dyn_str + dyn[i].d_un.d_val);
            retval = share_splite(&tmp_str, '/');
            if(0 != retval){
                goto free_out;
            }

            int tmp_len = G_strlen(GEEKOS_SO_ROOT_PATH) + G_strlen((dyn_str + 
                            dyn[i].d_un.d_val)) + 1;
            (*cnt)++;

            (*dynsym_so)[j] = (char*)Malloc(sizeof(char) * tmp_len);
            if(0 == (*dynsym_so)[j]){
                goto free_out;
            }
            strcat((*dynsym_so)[j], GEEKOS_SO_ROOT_PATH);
            strcat((*dynsym_so)[j], tmp_str);
            (*dynsym_so)[j][tmp_len - 1] = 0;
            ++j;
        }

    }

    retval = 0;
    return retval;

out:
    return retval;

free_out:
    free_dynsym_so(dynsym_so, *cnt);
    return retval;
}

void free_dynsym_so(char ***dynsym_so, int cnt)
{
    int i = 0;
    for(; i < cnt; ++i){
        safe_Free((*dynsym_so)[i]);
    }
    safe_Free((*dynsym_so));
}

int share_splite(char **str, char delim)
{
    int retval = -1;
    char *p = 0;

    if(0 == str){
        goto out;
    }

    p = (*str);
    while(0 != *p++){
        if(delim == *p){
            (*str) = (p + 1);
        }
    }
    retval = 0;
    return retval;

out:
    return retval;
}

int get_Sym_info(char *so_file_data, Sym_info *so_sym_info)
{
    int retval = -1;
    if(0 == so_file_data)
        goto out;

    int i = 0;
    int j = 0;
    int flag = 0;

    Elf32_Ehdr *ehdr;
    Elf32_Shdr *shdr;
    char *strtab;

    ehdr = (Elf32_Ehdr*)so_file_data;
    shdr = (Elf32_Shdr*)(so_file_data + ehdr->e_shoff);
    strtab = (char*)(so_file_data + shdr[ehdr->e_shstrndx].sh_offset);  

    for(; i < ehdr->e_shnum; i++)
    {
        if((SHT_DYNSYM == shdr[i].sh_type) && (0 == G_memcmp((strtab + 
            shdr[i].sh_name), DYNSYM_SCTIONG_NAME, 
            G_strlen(DYNSYM_SCTIONG_NAME))))
        {
            flag = 1;
            Elf32_Sym *tmp_sym = (Elf32_Sym *)(so_file_data + shdr[i].sh_offset);
            int sym_num = (shdr[i].sh_size / shdr[i].sh_entsize);
            if(99 <= sym_num || 0 == sym_num)
                goto out;

            char *func_name = (char*)(so_file_data + 
                (shdr[shdr[i].sh_link].sh_offset));
            if(0 == func_name)
                goto out;

            *so_sym_info = *(Sym_info*)Malloc(sizeof(Sym_info));
            if(!so_sym_info){
                goto out;
            }
            memset(so_sym_info, 0, sizeof(Sym_info));

            (*so_sym_info).num = sym_num;

            (*so_sym_info).func_name = (char**)Malloc(sizeof(char*) * sym_num);
            if(!(*so_sym_info).func_name){
                goto out_free;
            }
            memset((*so_sym_info).func_name, 0, sizeof(char**)*sym_num);

            (*so_sym_info).r_offset = (Elf32_Addr*)Malloc(sizeof(Elf32_Addr) * sym_num);
            if(!(*so_sym_info).r_offset){
                goto out_free;
            }
            memset((*so_sym_info).r_offset, 0, sizeof(Elf32_Addr) * sym_num);

            for(; j < sym_num; j++)
            {
                (*so_sym_info).func_name[j] = (func_name + 
                        tmp_sym[j].st_name);

                (*so_sym_info).r_offset[j] = tmp_sym[j].st_value;
            }
            retval = 1;
        }
    }
    if(0 == flag)
    {
        (*so_sym_info).num = 0;
        (*so_sym_info).func_name = 0;
        (*so_sym_info).r_offset = 0; 
        retval = 0;       
        return retval;
    }
    return retval;

out:
    (*so_sym_info).num = 0;
    (*so_sym_info).func_name = 0;
    (*so_sym_info).r_offset = 0;
    return -1;

out_free:
    free_Sym_info(so_sym_info);
    return retval;
}

void free_Sym_info(Sym_info *sym_info)
{
    sym_info->num = 0;
    safe_Free((*sym_info).func_name);
    safe_Free((*sym_info).r_offset);
    // safe_Free(sym_info);
}


int rel_exe_func(char **exe_file_data, Rel_info *rel_info, Sym_info *sym_info)
{

    int retval = -1;
    int i = 0;
    int j = 0;

    if(0 == (*exe_file_data) || 0 == rel_info || 0 == sym_info){
        goto out;
    }

    if(99 <= rel_info->num || 99 <= sym_info->num){
        goto out;
    }

    for(i = 0; i < sym_info->num; i++){
        for(j = 0; j < rel_info->num; j++){
            if(0 == G_memcmp((void*)sym_info->func_name[i], (void*)rel_info->func_name[j], 
                G_strlen(rel_info->func_name[j]))){
                Elf32_Addr *p = (Elf32_Addr*)((*exe_file_data) + rel_info->r_offset[j]);
                (*p) = sym_info->r_offset[i];
            }
        }
    }

    retval = 0;
    return retval;

out:
    return retval;
}